Table of contents

  • Importing Libraries
  • Configuring Visualization Parameters
  • Configuring Other Notebook Parameters
  • Pre-installing Custom Functions
  • Practicing in Stages
    • DICOM in Python
      • Extraction of DICOM File
      • Visualization of 2D DICOM Files
      • Visualization of 3D Data Stored as Multiple 2D DICOM Files

Importing Libraries¶

In [1]:
# Import PyDicom before using PyDicom functions

# DICOM (Digital Imaging and Communications in Medicine) is the standard protocol for
# the management and transmission of medical images and related data, and is used by
# many healthcare facilities

# PyDicom is a pure Python package for working with DICOM files
import pydicom
In [2]:
# The `pathlib` module is similar to the `os.path` module, but `pathlib` provides a more
# advanced and convenient interface than `os.path`

# It is possible to use `pathlib` to represent file paths as specialized `Path` objects
# instead of plain strings
from pathlib import Path
In [3]:
import numpy as np
In [4]:
import matplotlib as mpl
import matplotlib.pyplot as plt
In [5]:
from datetime import datetime
from functools import wraps
import itertools
import random
import reprlib
import sys

Configuring Visualization Parameters¶

In [6]:
%matplotlib inline
In [7]:
XINHUI = "#7a7374"
XUEBAI = "#fffef9"
YINBAI = "#f1f0ed"
YINHUI = "#918072"

figure_size = (16, 9)
In [8]:
custom_params = {
    "axes.axisbelow": True,
    "axes.edgecolor": YINBAI,
    "axes.facecolor": XUEBAI,
    "axes.grid": True,
    "axes.labelcolor": XINHUI,
    "axes.spines.right": False,
    "axes.spines.top": False,
    "axes.titlecolor": XINHUI,
    "figure.edgecolor": YINBAI,
    "figure.facecolor": XUEBAI,
    "grid.alpha": 0.8,
    "grid.color": YINBAI,
    "grid.linestyle": "--",
    "grid.linewidth": 1.2,
    "legend.edgecolor": YINHUI,
    "patch.edgecolor": XUEBAI,
    "patch.force_edgecolor": True,
    "text.color": XINHUI,
    "xtick.color": YINHUI,
    "ytick.color": YINHUI,
}

mpl.rcParams.update(custom_params)

Configuring Other Notebook Parameters¶

In [9]:
reprlib_rules = reprlib.Repr()
reprlib_rules.maxother = 250

Pre-installing Custom Functions¶

In [10]:
sys.path.append("../")
In [11]:
from Modules import *

Practicing in Stages¶

DICOM in Python¶

Extraction of DICOM File¶

In [12]:
# The dataset used in this practice project is a very small subset of CT images extracted from
# the Cancer Imaging Archive (TCIA), which contains middle slices of all CT images where
# valid age, modality, and contrast tags can be found

# The dataset is provided by the Kaggel Datasets called CT Medical Imaging
# (https://www.kaggle.com/datasets/kmader/siim-medical-images),
# license type is Attribution 3.0 Unported (CC BY 3.0)
# https://creativecommons.org/licenses/by/3.0

# The link to the TCIA archive of the full dataset is
# https://wiki.cancerimagingarchive.net/display/Public/TCGA-LUAD
dir_path = "../Datasets/Kaggle - CT Medical Images/dicom_dir/"
sample_dcm = "ID_0000_AGE_0060_CONTRAST_1_CT.dcm"
# The main function of PyDicom to read and parse DICOM files is `read_file`
dicom_file = pydicom.read_file(dir_path + sample_dcm)

tabulation = Form_Generator()
tabulation.heading_printer("Initial understanding of DICOM file")

statements = [
    """
dir_path = "../Datasets/Kaggle - CT Medical Images/dicom_dir/"
sample_dcm = "ID_0000_AGE_0060_CONTRAST_1_CT.dcm"
dicom_file = pydicom.read_file(dir_path + sample_dcm)
"""
]
tabulation.statement_generator(statements)

variables = ["dicom_file"]
values = [str(reprlib_rules.repr(dicom_file))]
tabulation.variable_generator(variables, values)

expressions = [
    "dicom_file[0x0028, 0x0010]",
    "dicom_file[0x0028, 0x0011]",
    "dicom_file[0x0018, 0x0015]",
    "dicom_file.Rows",
    "dicom_file.Columns",
    "dicom_file.BodyPartExamined",
    "dicom_file.keys()",
    "dicom_file.values()",
    "dicom_file.dir()",
    'dicom_file.dir("Image")',
]
results = [
    str(dicom_file[0x0028, 0x0010]),
    str(dicom_file[0x0028, 0x0011]),
    str(dicom_file[0x0018, 0x0015]),
    str(dicom_file.Rows),
    str(dicom_file.Columns),
    str(dicom_file.BodyPartExamined),
    str(reprlib_rules.repr(dicom_file.keys())),
    str(reprlib_rules.repr(dicom_file.values())),
    str(reprlib_rules.repr(dicom_file.dir())),
    str(reprlib_rules.repr(dicom_file.dir("Image"))),
]
tabulation.expression_generator(expressions, results, 1)
Initial understanding of DICOM file

    +-------------------------------------------------------+
    | Statement                                             |
    +-------------------------------------------------------+
    | dir_path = "../Datasets/Kaggle - CT Medical           |
    |     Images/dicom_dir/"                                |
    | sample_dcm = "ID_0000_AGE_0060_CONTRAST_1_CT.dcm"     |
    | dicom_file = pydicom.read_file(dir_path + sample_dcm) |
    +-------------------------------------------------------+
    +------------+------------------------------------------------+
    | Variable   | Value                                          |
    +------------+------------------------------------------------+
    | dicom_file | Dataset.file_meta                              |
    |            |         -------------------------------        |
    |            | (0002, 0000) File Meta Information Group       |
    |            |         Length  UL: 194                        |
    |            | (0002, 0001) Fil...Group Length                |
    |            |                 UL: 524296                     |
    |            | (7fe0, 0010) Pixel Data                        |
    |            |           OW: Array of 524288 elements         |
    +------------+------------------------------------------------+
    +-----------------------------+-------------------------------+
    | Expression                  | Result                        |
    +-----------------------------+-------------------------------+
    | dicom_file[0x0028, 0x0010]  | (0028, 0010) Rows             |
    |                             |                     US: 512   |
    | dicom_file[0x0028, 0x0011]  | (0028, 0011) Columns          |
    |                             |                     US: 512   |
    | dicom_file[0x0018, 0x0015]  | (0018, 0015) Body Part        |
    |                             |  Examined                     |
    |                             |  CS: 'CHEST'                  |
    | dicom_file.Rows             | 512                           |
    | dicom_file.Columns          | 512                           |
    | dicom_file.BodyPartExamined | CHEST                         |
    | dicom_file.keys()           | dict_keys([(0008, 0000),      |
    |                             |  (0008, 0005), (0008, 0008),  |
    |                             |  (0008, 0016), (0008, 0018),  |
    |                             |  (0008, 0020), (0008, 0021),  |
    |                             |  (0008, 0022), ...095, 0010), |
    |                             |  (0097, 0000), (0097, 0010),  |
    |                             |  (0099, 0000), (0099, 0010),  |
    |                             |  (7003, 0000), (7003, 0010),  |
    |                             |  (7fe0, 0000), (7fe0, 0010)]) |
    | dicom_file.values()         | dict_values([(0008, 0000)     |
    |                             |  Group Length                 |
    |                             |         UL: 430, (0008, 0005) |
    |                             |  Specific Character Set       |
    |                             |         CS:...up Length       |
    |                             |                   UL: 524296, |
    |                             |  (7fe0, 0010) Pixel Data      |
    |                             |                      OW:      |
    |                             |  Array of 524288 elements])   |
    | dicom_file.dir()            | ['AccessionNumber',           |
    |                             |  'AcquisitionDate',           |
    |                             |  'AcquisitionNumber',         |
    |                             |  'AcquisitionTime',           |
    |                             |  'BitsAllocated',             |
    |                             |  'BitsStored', ...]           |
    | dicom_file.dir("Image")     | ['ImageDimensions',           |
    |                             |  'ImageFormat',               |
    |                             |  'ImageGeometryType',         |
    |                             |  'ImageLocation',             |
    |                             |  'ImageOrientation',          |
    |                             |  'ImageOrientationPatient',   |
    |                             |  ...]                         |
    +-----------------------------+-------------------------------+
In [13]:
# DICOM data has an attribute called `pixel_array` that provides more useful pixel data
# for uncompressed images that can be passed to the graphics library for viewing

# To use this attribute, the system must have the NumPy numeric package installed,
# since `pixel_array` returns a NumPy array
ct = dicom_file.pixel_array

tabulation = Form_Generator()
tabulation.heading_printer("Getting pixel data from DICOM file")

statements = ["ct = dicom_file.pixel_array"]
tabulation.statement_generator(statements)

variables = ["ct"]
values = [str(reprlib_rules.repr(ct))]
tabulation.variable_generator(variables, values)

expressions = ["ct.shape"]
results = [str(ct.shape)]
tabulation.expression_generator(expressions, results)
Getting pixel data from DICOM file

    +-----------------------------+
    | Statement                   |
    +-----------------------------+
    | ct = dicom_file.pixel_array |
    +-----------------------------+
    +----------+------------------------------------------------+
    | Variable | Value                                          |
    +----------+------------------------------------------------+
    | ct       | array([[0, 0, 0, ..., 0, 0, 0],                |
    |          |        [0, 0, 0, ..., 0, 0, 0],                |
    |          |        [0, 0, 0, ..., 0, 0, 0],                |
    |          |        ...,                                    |
    |          |        [0, 0, 0, ..., 0, 0, 0],                |
    |          |        [0, 0, 0, ..., 0, 0, 0],                |
    |          |        [0, 0, 0, ..., 0, 0, 0]], dtype=uint16) |
    +----------+------------------------------------------------+
    +------------+------------+
    | Expression | Result     |
    +------------+------------+
    | ct.shape   | (512, 512) |
    +------------+------------+

Visualization of 2D DICOM Files¶

In [14]:
def image_display(image, ax, title, cmap):
    ax.imshow(image, cmap)
    ax.grid(False)
    ax.set_title(title, loc="center", pad=10)
    x_ticks = list(range(0, image.shape[1], 50))
    y_ticks = list(range(0, image.shape[0], 50))
    ax.set(xticks=x_ticks, xticklabels=x_ticks,
           yticks=y_ticks, yticklabels=y_ticks)
    ax.set_xlim(left=0)
    ax.set_ylim(top=0)
    ax.minorticks_on()
    return ax


# Path classes are divided between pure paths, which provide purely computational
# operations without I/O, and concrete paths, which inherit from pure paths but also
# provide I/O operations
path_object = Path(dir_path)
# `PurePath.name` returns a string representing the final path component, excluding
# the drive and root directory (if any)

# When a path points to a directory, `Path.iterdir` generates a path object of the directory's
# contents

# `Path.is_file` returns True if the path points to a normal file or to a symbolic link
# to a normal file, False if it points to another type of file
random_dicom_path = random.choice(
    [
        file.name
        for file in path_object.iterdir()
        if file.is_file() & (pydicom.read_file(file).BodyPartExamined != "CHEST")
    ]
)

random_dicom_file = pydicom.read_file(dir_path + random_dicom_path)

plt.rcParams["figure.figsize"] = (
    figure_size[0] / 3 * 2, figure_size[1] / 4 * 5)

fig, axs = plt.subplots(nrows=2, ncols=2)

image_display(
    ct,
    axs[0, 0],
    f"{dicom_file.BodyPartExamined.capitalize()} CT medical image displayed using "
    "colormap 'gray'",
    cmap="gray",
)

image_display(
    ct,
    axs[0, 1],
    f"{dicom_file.BodyPartExamined.capitalize()} CT medical image displayed using "
    "colormap 'bone'",
    cmap="bone",
)

image_display(
    random_dicom_file.pixel_array,
    axs[1, 0],
    f"{random_dicom_file.BodyPartExamined.capitalize()} CT medical image displayed using "
    "colormap 'gray'",
    cmap="gray",
)

image_display(
    random_dicom_file.pixel_array,
    axs[1, 1],
    f"{random_dicom_file.BodyPartExamined.capitalize()} CT medical image displayed using "
    "colormap 'bone'",
    cmap="bone",
)


fig.suptitle(
    "Visual Comparison of CT Medical Image Display Using Different Colormaps",
    fontsize="x-large",
    x=0.5,
    y=0,
)

plt.tight_layout()
plt.show()
In [15]:
def annotation_color(cmap):
    cmap_color = mpl.colormaps[cmap]
    return cmap_color(0.85)


def image_orientation(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        Left, Right, Anterior, Posterior, Head, Feet = "L", "R", "A", "P", "H", "F"
        axial_plane = [Anterior, Right, Posterior, Left]
        coronal_plane = [Right, Head, Left, Feet]
        sagittal_plane = [Anterior, Head, Posterior, Feet]
        plane = func(*args, **kwargs)
        # Patient Position (0018,5100) specifies the position of the patient relative to
        # the space of the imaging device and is used for annotation purposes only,
        # but does not provide a precise mathematical relationship between the patient and
        # the imaging device
        patient_position = dicom_file.PatientPosition
        if plane == "Axial":
            term_list = ["S", "DR", "P", "DL"]
            top = 4 - term_list.index(patient_position[2:])
            if patient_position.startswith("HF"):
                right = top + 1
                left = right + 2
            else:
                left = top + 1
                right = left + 2
            bottom = top + 2
            top, right, bottom, left = [
                axial_plane[index] if index < 4 else axial_plane[index - 4]
                for index in [top, right, bottom, left]
            ]
        elif plane == "Coronal":
            term_list = ["DL", "DR"]
            top = term_list.index(patient_position[2:]) * 2
            if patient_position.startswith("AF"):
                right = top + 1
                left = right + 2
            else:
                left = top + 1
                right = left + 2
            bottom = top + 2
            top, right, bottom, left = [
                coronal_plane[index] if index < 4 else coronal_plane[index - 4]
                for index in [top, right, bottom, left]
            ]
        elif plane == "Sagittal":
            term_list = ["LF", "RF"]
            if patient_position.endswith("S"):
                top = 0
                right = term_list.index(patient_position[:2]) * 2 + 1
                left = right + 2
            else:
                top = 2
                left = term_list.index(patient_position[:2]) * 2 + 1
                right = left + 2
            bottom = top + 2
            top, right, bottom, left = [
                sagittal_plane[index] if index < 4 else sagittal_plane[index - 4]
                for index in [top, right, bottom, left]
            ]
        else:
            top, right, bottom, left = np.repeat("", 4)
        return plane, top, right, bottom, left

    return wrapper


def orientation_annotator(func):
    @wraps(func)
    def wrapper(ax, cmap, dicom_file, fontsize_adjustment):
        plane, top, right, bottom, left = func(dicom_file)
        ax.text(
            0.4875,
            0.95,
            top,
            transform=ax.transAxes,
            fontweight="heavy",
            fontsize=11 + fontsize_adjustment,
            fontfamily="monospace",
            c=annotation_color(cmap),
        )
        ax.text(
            0.4875,
            0.025,
            bottom,
            transform=ax.transAxes,
            fontweight="heavy",
            fontsize=11 + fontsize_adjustment,
            fontfamily="monospace",
            c=annotation_color(cmap),
        )
        ax.text(
            0.95,
            0.4875,
            right,
            transform=ax.transAxes,
            fontweight="heavy",
            fontsize=11 + fontsize_adjustment,
            fontfamily="monospace",
            c=annotation_color(cmap),
        )
        ax.text(
            0.025,
            0.4875,
            left,
            transform=ax.transAxes,
            fontweight="heavy",
            fontsize=11 + fontsize_adjustment,
            fontfamily="monospace",
            c=annotation_color(cmap),
        )
        return plane

    return wrapper


def first_max(list):
    max_element = list[0]
    for element in list[1:]:
        if len(element) > len(max_element):
            max_element = element
    return max_element


def text_aligner(text):
    line_list = text.split("\n")
    max_length = len(first_max(line_list))
    line_list = [
        (":" + " " * (max_length - len(line))).join(line.split(":"))
        if len(line) < max_length
        else line
        for line in line_list
    ]
    text = "\n".join(line_list)
    return text


def plane_information_annotator(func):
    @wraps(func)
    def wrapper(ax, cmap, dicom_file, fontsize_adjustment):
        plane = func(ax, cmap, dicom_file, fontsize_adjustment)
        text = f"Image: {dicom_file.Columns} x {dicom_file.Rows}"
        text += f"\nImage Plane: {plane}"
        text += f"\nPatient Position: {dicom_file.PatientPosition}"
        text += f"\nSlice Thickness: {dicom_file.SliceThickness:.1f} mm"
        text += f"\nSlice Location: {dicom_file.SliceLocation:.1f} mm"
        ax.text(
            0.575,
            0.825,
            text_aligner(text),
            transform=ax.transAxes,
            fontsize=9 + fontsize_adjustment,
            fontfamily="monospace",
            c=annotation_color(cmap),
        )

    return wrapper


@plane_information_annotator
@orientation_annotator
@image_orientation
def get_plane_information(dicom_file):
    # The DICOM standard defines a patient-oriented reference coordinate system (RCS) that
    # enables the user to measure the position and orientation of the image relative to
    # the patient

    # Image Orientation (Patient) (0020,0037) specifies the cosine of the orientation of
    # the first row and column relative to the patient, which should be provided as a pair,
    # where the row values for the x, y, and z axes are followed by the column values for
    # the x, y, and z axes

    # The orientation of the axes is determined solely by the orientation of the patient, i.e.,
    # the RCS and Image Orientation (Patient) (0020,0037) values specify the orientation of
    # the image frame rows and columns
    IOP = dicom_file.ImageOrientationPatient
    row_xyz = IOP[:3]
    column_xyz = IOP[3:]
    # `numpy.cross` returns the cross product of two vector arrays

    # The geometric interpretation of the cross product is that two vectors determine a plane,
    # and the cross product points in a direction different from both vectors

    # The Patient-Based Coordinate System is a right handed system, i.e., the vector
    # cross product of a unit vector along the positive x-axis and a unit vector along
    # the positive y-axis is equal to a unit vector along the positive z-axis
    plane = [abs(round(x)) for x in np.cross(row_xyz, column_xyz)]
    # The Image Type (0008,0008) identifies important image identification features
    # and it returns the Pixel Data Characteristics, Patient Examination Characteristics,
    # Modality Specific Characteristics, and Implementation Specific Identifiers

    # While the third value (Modality Specific Characteristics) should identify any
    # specialization specific to the Image Information Object Definition (IOD), it
    # and the values that follow it are not mandatory, unlike the first two values,
    # and therefore some DICOM data does not have this value
    if plane[0] == 1 and plane[1] == 0 and plane[2] == 0:
        return "Sagittal"
    elif plane[0] == 0 and plane[1] == 1 and plane[2] == 0:
        return "Coronal"
    elif plane[0] == 0 and plane[1] == 0 and plane[2] == 1:
        return "Axial"
    else:
        "Unknown"


def get_patient_information(ax, cmap, dicom_file, fontsize_adjustment, patien_age):
    text = f"Patient ID: {dicom_file.PatientID}"
    text += f"\nPatient's Sex: {dicom_file.PatientSex}"
    try:
        patien_age = dicom_file.PatientAge.strip(str(0))[:-1]
    except AttributeError:
        patien_age = patien_age
    text += f"\nPatient's Age: {patien_age} y"
    text += f"\nModality: {dicom_file.Modality}"
    text += f"\nBody Part Examined: {dicom_file.BodyPartExamined}"
    ax.text(
        0.04,
        0.825,
        text_aligner(text),
        transform=ax.transAxes,
        fontsize=9 + fontsize_adjustment,
        fontfamily="monospace",
        c=annotation_color(cmap),
    )


def get_date_information(ax, cmap, dicom_file, fontsize_adjustment):
    date = datetime.strptime(dicom_file.StudyDate,
                             "%Y%m%d").strftime("%Y.%m.%d")
    text = f"Study Date: {date}"
    ax.text(
        0.04,
        0.05,
        text,
        transform=ax.transAxes,
        fontsize=7 + fontsize_adjustment,
        fontfamily="monospace",
        c=annotation_color(cmap),
    )


def get_manufacturer_information(ax, cmap, dicom_file, fontsize_adjustment):
    text = f"Manufacturer: {dicom_file.Manufacturer}"
    ax.text(
        0.525,
        0.05,
        text.rjust(36),
        transform=ax.transAxes,
        fontsize=7 + fontsize_adjustment,
        fontfamily="monospace",
        c=annotation_color(cmap),
    )


def DICOM_2D_image(
    dicom_file, ax, title, cmap, fontsize_adjustment=0, patien_age="xxx"
):
    ax.imshow(dicom_file.pixel_array, cmap)
    ax.grid(False)
    ax.set_title(title, loc="center", pad=10)
    ax.set(xticks=[], yticks=[], frame_on=False)
    get_patient_information(ax, cmap, dicom_file,
                            fontsize_adjustment, patien_age)
    get_plane_information(ax, cmap, dicom_file, fontsize_adjustment)
    get_date_information(ax, cmap, dicom_file, fontsize_adjustment)
    get_manufacturer_information(ax, cmap, dicom_file, fontsize_adjustment)


plt.rcParams["figure.figsize"] = (
    figure_size[0] / 3 * 2, figure_size[1] / 4 * 5)

fig, axs = plt.subplots(nrows=2, ncols=2)

DICOM_2D_image(
    dicom_file,
    axs[0, 0],
    f"{dicom_file.BodyPartExamined.capitalize()} CT medical image displayed using "
    "colormap 'gray'",
    cmap="gray",
)

DICOM_2D_image(
    dicom_file,
    axs[0, 1],
    f"{dicom_file.BodyPartExamined.capitalize()} CT medical image displayed using "
    "colormap 'bone'",
    cmap="bone",
)

DICOM_2D_image(
    random_dicom_file,
    axs[1, 0],
    f"{random_dicom_file.BodyPartExamined.capitalize()} CT medical image displayed using "
    "colormap 'gray'",
    cmap="gray",
)

DICOM_2D_image(
    random_dicom_file,
    axs[1, 1],
    f"{random_dicom_file.BodyPartExamined.capitalize()} CT medical image displayed using "
    "colormap 'bone'",
    cmap="bone",
)


fig.suptitle(
    "Visualization of CT Medical Image Display with Supplementary Information",
    fontsize="x-large",
    x=0.5,
    y=0,
)

plt.tight_layout()
plt.show()

Visualization of 3D Data Stored as Multiple 2D DICOM Files¶

In [16]:
# The dataset used in this practical project is the MRI DICOM dataset of the head of
# a 52-year-old normal male mathematics professor

# The subject of the experiment is the author, who suffers from small vertical strabismus
# (hypertropia), which is a misalignment of the eyes, which can be seen in this dataset

# This dataset was provided by Lionheart, William R.B. in 2015, available online
# (https://zenodo.org/record/16956 or http://doi.org/10.5281/zenodo.16956),
# license type is Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
# https://creativecommons.org/licenses/by-sa/4.0
path_to_head_mri = Path(
    "../Datasets/An MRI Dicom Data Set of the Head of a Normal Male Human Aged 52/"
    "ST000000/SE000001/"
)
# `Path.glob` selects the given relative pattern in the directory represented by the given path
# and yields all matching files (of any type)

# Since `Path.glob` returns a generator, it is converted to a list here for easy display
all_files = list(path_to_head_mri.glob("*"))

mri_data = []

for path in all_files:
    # Read the individual DICOM files one by one in the list returned by the path generator
    data = pydicom.read_file(path)
    mri_data.append(data)

tabulation = Form_Generator()
tabulation.heading_printer(
    "Reading all DICOM files that match the specified pattern")

statements = [
    """
path_to_head_mri = Path(
    "../Datasets/An MRI Dicom Data Set of the Head of a Normal Male Human Aged 52/"
    "ST000000/SE000001/"
)
all_files = list(path_to_head_mri.glob("*"))

mri_data = []

for path in all_files:
    data = pydicom.read_file(path)
    mri_data.append(data)
"""
]
tabulation.statement_generator(statements)

variables = ["path_to_head_mri", "all_files"]
values = [str(path_to_head_mri), str(reprlib_rules.repr(all_files))]
tabulation.variable_generator(variables, values, 1)

expressions = [
    "all_files[0].parts",
    "all_files[0].parent",
    "all_files[0].name",
    "all_files[0].suffixes",
    "all_files[0].stem",
    "len(all_files)",
    "mri_data[0]",
    "len(mri_data)",
]
results = [
    str(all_files[0].parts),
    str(all_files[0].parent),
    str(all_files[0].name),
    str(all_files[0].suffixes),
    str(all_files[0].stem),
    str(len(all_files)),
    str(reprlib_rules.repr(mri_data[0])),
    str(len(mri_data)),
]
tabulation.expression_generator(expressions, results, 1)
Reading all DICOM files that match the specified pattern

    +---------------------------------------------------------+
    | Statement                                               |
    +---------------------------------------------------------+
    | path_to_head_mri = Path(                                |
    |     "../Datasets/An MRI Dicom Data Set of the Head of a |
    |     Normal Male Human Aged 52/"                         |
    |     "ST000000/SE000001/"                                |
    | )                                                       |
    | all_files = list(path_to_head_mri.glob("*"))            |
    |                                                         |
    | mri_data = []                                           |
    |                                                         |
    | for path in all_files:                                  |
    |     data = pydicom.read_file(path)                      |
    |     mri_data.append(data)                               |
    +---------------------------------------------------------+
    +------------------+------------------------------------------+
    | Variable         | Value                                    |
    +------------------+------------------------------------------+
    | path_to_head_mri | ../Datasets/An MRI Dicom Data Set of the |
    |                  |  Head of a Normal Male Human Aged        |
    |                  |  52/ST000000/SE000001                    |
    | all_files        | [PosixPath('../Datasets/An MRI Dicom     |
    |                  |  Data Set of the Head of a Normal Male   |
    |                  |  Human Aged                              |
    |                  |  52/ST000000/SE000001/MR000000'),        |
    |                  |  PosixPath('../Datasets/An MRI Dicom     |
    |                  |  Data Set of the Head of a Normal Male   |
    |                  |  Human Aged                              |
    |                  |  52/ST000000/SE000001/MR000007'),        |
    |                  |  PosixPath('../Datasets/An MRI Dicom     |
    |                  |  Data Set of the Head of a Normal Male   |
    |                  |  Human Aged                              |
    |                  |  52/ST000000/SE000001/MR000009'),        |
    |                  |  PosixPath('../Datasets/An MRI Dicom     |
    |                  |  Data Set of the Head of a Normal Male   |
    |                  |  Human Aged                              |
    |                  |  52/ST000000/SE000001/MR000008'),        |
    |                  |  PosixPath('../Datasets/An MRI Dicom     |
    |                  |  Data Set of the Head of a Normal Male   |
    |                  |  Human Aged                              |
    |                  |  52/ST000000/SE000001/MR000006'),        |
    |                  |  PosixPath('../Datasets/An MRI Dicom     |
    |                  |  Data Set of the Head of a Normal Male   |
    |                  |  Human Aged                              |
    |                  |  52/ST000000/SE000001/MR000001'), ...]   |
    +------------------+------------------------------------------+
    +-----------------------+-------------------------------------+
    | Expression            | Result                              |
    +-----------------------+-------------------------------------+
    | all_files[0].parts    | ('..', 'Datasets', 'An MRI Dicom    |
    |                       |  Data Set of the Head of a Normal   |
    |                       |  Male Human Aged 52', 'ST000000',   |
    |                       |  'SE000001', 'MR000000')            |
    | all_files[0].parent   | ../Datasets/An MRI Dicom Data Set   |
    |                       |  of the Head of a Normal Male Human |
    |                       |  Aged 52/ST000000/SE000001          |
    | all_files[0].name     | MR000000                            |
    | all_files[0].suffixes | []                                  |
    | all_files[0].stem     | MR000000                            |
    | len(all_files)        | 27                                  |
    | mri_data[0]           | Dataset.file_meta                   |
    |                       |  -------------------------------    |
    |                       | (0002, 0000) File Meta Information  |
    |                       |  Group Length  UL: 214              |
    |                       | (0002, 0001) Fil...Group Length     |
    |                       |                     UL: 131084      |
    |                       | (7fe0, 0010) Pixel Data             |
    |                       |               OW: Array of 131072   |
    |                       |  elements                           |
    | len(mri_data)         | 27                                  |
    +-----------------------+-------------------------------------+
In [17]:
# DICOM files may not be sorted according to their actual image positions, this can
# be verified by checking Slice Location (0020,1041)

# Slice Location (0020,1041) identifies the relative position of the image plane in millimeters
unordered_slices = {
    index: float(slice.SliceLocation) for index, slice in enumerate(mri_data)
}

# For better viewing and processing of 3D data stored with multiple 2D DICOM files,
# these files must be sorted; otherwise the complete scanned image will become cluttered
# and useless
mri_data_ordered = sorted(mri_data, key=lambda slice: slice.SliceLocation)

ordered_slices = {
    index: float(slice.SliceLocation) for index, slice in enumerate(mri_data_ordered)
}


tabulation = Form_Generator()
tabulation.heading_printer("Sorting of read DICOM files")

statements = [
    """
unordered_slices = {
    index: float(slice.SliceLocation) for index, slice in enumerate(mri_data)
}

mri_data_ordered = sorted(mri_data, key=lambda slice: slice.SliceLocation)

ordered_slices = {
    index: float(slice.SliceLocation) for index, slice in enumerate(mri_data_ordered)
}
"""
]
tabulation.statement_generator(statements)

variables = ["unordered_slices", "ordered_slices"]
values = [
    str(unordered_slices),
    str(ordered_slices),
]
tabulation.variable_generator(variables, values, 1)

expressions = [
    "mri_data_ordered[0]",
    "len(mri_data_ordered)",
    "len(unordered_slices)",
    "len(ordered_slices)",
]
results = [
    str(reprlib_rules.repr(mri_data_ordered[0])),
    str(len(mri_data_ordered)),
    str(len(unordered_slices)),
    str(len(ordered_slices)),
]
tabulation.expression_generator(expressions, results, 1)
Sorting of read DICOM files

    +-----------------------------------------------------------+
    | Statement                                                 |
    +-----------------------------------------------------------+
    | unordered_slices = {                                      |
    |     index: float(slice.SliceLocation) for index, slice in |
    |     enumerate(mri_data)                                   |
    | }                                                         |
    |                                                           |
    | mri_data_ordered = sorted(mri_data, key=lambda slice:     |
    |     slice.SliceLocation)                                  |
    |                                                           |
    | ordered_slices = {                                        |
    |     index: float(slice.SliceLocation) for index, slice in |
    |     enumerate(mri_data_ordered)                           |
    | }                                                         |
    +-----------------------------------------------------------+
    +------------------+------------------------------------------+
    | Variable         | Value                                    |
    +------------------+------------------------------------------+
    | unordered_slices | {0: 0.0, 1: 41.9999963629367, 2:         |
    |                  |  53.9999958207213, 3: 47.9999970362677,  |
    |                  |  4: 35.9999959546749, 5:                 |
    |                  |  5.99999663091323, 6: 137.999998321624,  |
    |                  |  7: 143.999998928727, 8:                 |
    |                  |  71.9999961590453, 9: 89.9999955528687,  |
    |                  |  10: 83.9999967682912, 11:               |
    |                  |  77.999996227574, 12: 149.999999502083,  |
    |                  |  13: 131.999997780749, 14:               |
    |                  |  23.9999946081714, 15: 17.9999979772582, |
    |                  |  16: 11.9999973042441, 17:               |
    |                  |  29.9999952815023, 18: 107.999995419197, |
    |                  |  19: 119.999996566542, 20:               |
    |                  |  95.9999960937442, 21: 65.9999961939969, |
    |                  |  22: 59.9999962290673, 23:               |
    |                  |  101.999994745866, 24: 125.999997173645, |
    |                  |  25: 155.999992554172, 26:               |
    |                  |  113.999995959439}                       |
    | ordered_slices   | {0: 0.0, 1: 5.99999663091323, 2:         |
    |                  |  11.9999973042441, 3: 17.9999979772582,  |
    |                  |  4: 23.9999946081714, 5:                 |
    |                  |  29.9999952815023, 6: 35.9999959546749,  |
    |                  |  7: 41.9999963629367, 8:                 |
    |                  |  47.9999970362677, 9: 53.9999958207213,  |
    |                  |  10: 59.9999962290673, 11:               |
    |                  |  65.9999961939969, 12: 71.9999961590453, |
    |                  |  13: 77.999996227574, 14:                |
    |                  |  83.9999967682912, 15: 89.9999955528687, |
    |                  |  16: 95.9999960937442, 17:               |
    |                  |  101.999994745866, 18: 107.999995419197, |
    |                  |  19: 113.999995959439, 20:               |
    |                  |  119.999996566542, 21: 125.999997173645, |
    |                  |  22: 131.999997780749, 23:               |
    |                  |  137.999998321624, 24: 143.999998928727, |
    |                  |  25: 149.999999502083, 26:               |
    |                  |  155.999992554172}                       |
    +------------------+------------------------------------------+
    +-----------------------+-------------------------------------+
    | Expression            | Result                              |
    +-----------------------+-------------------------------------+
    | mri_data_ordered[0]   | Dataset.file_meta                   |
    |                       |  -------------------------------    |
    |                       | (0002, 0000) File Meta Information  |
    |                       |  Group Length  UL: 214              |
    |                       | (0002, 0001) Fil...Group Length     |
    |                       |                     UL: 131084      |
    |                       | (7fe0, 0010) Pixel Data             |
    |                       |               OW: Array of 131072   |
    |                       |  elements                           |
    | len(mri_data_ordered) | 27                                  |
    | len(unordered_slices) | 27                                  |
    | len(ordered_slices)   | 27                                  |
    +-----------------------+-------------------------------------+
In [18]:
# Fill the 3D array in a slice-per-slice manner
full_volume = [slice.pixel_array for slice in mri_data_ordered]
full_volume = np.array(full_volume)

tabulation = Form_Generator()
tabulation.heading_printer(
    "One-time extraction of the overall 3D pixel array for all slices"
)

statements = [
    """
full_volume = [slice.pixel_array for slice in mri_data_ordered]
full_volume = np.array(full_volume)
"""
]
tabulation.statement_generator(statements)

variables = ["full_volume"]
values = [str(reprlib_rules.repr(full_volume))]
tabulation.variable_generator(variables, values)

expressions = [
    "full_volume.shape",
]
results = [str(full_volume.shape)]
tabulation.expression_generator(expressions, results)
One-time extraction of the overall 3D pixel array for all slices

    +-----------------------------------------------+
    | Statement                                     |
    +-----------------------------------------------+
    | full_volume = [slice.pixel_array for slice in |
    |     mri_data_ordered]                         |
    | full_volume = np.array(full_volume)           |
    +-----------------------------------------------+
    +-------------+------------------------------------+
    | Variable    | Value                              |
    +-------------+------------------------------------+
    | full_volume | array([[[0, 0, 0, ..., 0, 0, 0],   |
    |             |         [0, 0, 0, ..., 0, 0, 0],   |
    |             |         [0, 0, 0, ..., 0, 0, 0],   |
    |             |         ...,                       |
    |             |         [0,...     ...,            |
    |             |         [0, 0, 0, ..., 0, 0, 0],   |
    |             |         [0, 0, 0, ..., 0, 0, 0],   |
    |             |         [0, 0, 0, ..., 0, 0, 0]]], |
    |             |         dtype=uint16)              |
    +-------------+------------------------------------+
    +-------------------+----------------+
    | Expression        | Result         |
    +-------------------+----------------+
    | full_volume.shape | (27, 256, 256) |
    +-------------------+----------------+
In [19]:
fig, axis = plt.subplots(9, 3, figsize=(figure_size[0] / 3 * 2, figure_size[1] * 4))

for slice_counter, (i, j) in enumerate(itertools.product(range(9), range(3))):
    DICOM_2D_image(
        mri_data_ordered[slice_counter],
        axis[i][j],
        "",
        cmap="bone",
        fontsize_adjustment=-2.5,
        patien_age="52",
    )
plt.tight_layout()
plt.show()
In [ ]: